{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T08:02:39.362298700Z",
     "start_time": "2024-07-18T08:02:28.460738Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.9.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.2\n",
      "sklearn 1.5.0\n",
      "torch 2.3.1+cpu\n",
      "cpu\n"
     ]
    }
   ],
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 加载数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T08:03:06.683128100Z",
     "start_time": "2024-07-18T08:03:04.366903700Z"
    }
   },
   "outputs": [],
   "source": [
    "from torchvision import datasets\n",
    "from torchvision.transforms import ToTensor\n",
    "\n",
    "# fashion_mnist图像分类数据集\n",
    "train_ds = datasets.FashionMNIST(\n",
    "    root=\"data\",\n",
    "    train=True,\n",
    "    download=True,\n",
    "    transform=ToTensor()\n",
    ")\n",
    "\n",
    "test_ds = datasets.FashionMNIST(\n",
    "    root=\"data\",\n",
    "    train=False,\n",
    "    download=True,\n",
    "    transform=ToTensor()\n",
    ")\n",
    "\n",
    "# torchvision 数据集里没有提供训练集和验证集的划分\n",
    "# 当然也可以用 torch.utils.data.Dataset 实现人为划分\n",
    "# 从数据集到dataloader\n",
    "train_loader = torch.utils.data.DataLoader(train_ds, batch_size=16, shuffle=True)\n",
    "val_loader = torch.utils.data.DataLoader(test_ds, batch_size=16, shuffle=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T08:03:21.794152100Z",
     "start_time": "2024-07-18T08:03:11.326036600Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(tensor([0.2860]), tensor([0.3205]))\n"
     ]
    }
   ],
   "source": [
    "from torchvision.transforms import Normalize\n",
    "\n",
    "# 遍历train_ds得到每张图片，计算每个通道的均值和方差\n",
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds: # 遍历每张图片,img.shape=[1,28,28]\n",
    "        mean += img.mean(dim=(1, 2))\n",
    "        std += img.std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "\n",
    "print(cal_mean_std(train_ds))\n",
    "# 0.2860， 0.3205\n",
    "transforms = nn.Sequential(\n",
    "    Normalize([0.2860], [0.3205]) # 这里的均值和标准差是通过train_ds计算得到的\n",
    ")\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义模型\n",
    "\n",
    "这里我们没有用`nn.Linear`的默认初始化，而是采用了xavier均匀分布去初始化全连接层的权重\n",
    "\n",
    "xavier初始化出自论文 《Understanding the difficulty of training deep feedforward neural networks》，适用于使用`tanh`和`sigmoid`激活函数的方法。当然，我们这里的模型采用的是`relu`激活函数，采用He初始化（何凯明初始化）会更加合适。感兴趣的同学可以自己动手修改并比对效果。\n",
    "\n",
    "|神经网络层数|初始化方式|early stop at epoch| val_loss | vla_acc|\n",
    "|-|-|-|-|-|\n",
    "|20|默认|\n",
    "|20|xaviier_uniform|\n",
    "|20|he_uniform|\n",
    "|...|\n",
    "\n",
    "He初始化出自论文 《Delving deep into rectifiers: Surpassing human-level performance on ImageNet classification》"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T08:18:01.487539200Z",
     "start_time": "2024-07-18T08:18:01.464930Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Linear_00\tparamerters num: 78400\n",
      "Linear_00\tparamerters num: 100\n",
      "Linear_01\tparamerters num: 10000\n",
      "Linear_01\tparamerters num: 100\n",
      "Linear_02\tparamerters num: 10000\n",
      "Linear_02\tparamerters num: 100\n",
      "Linear_03\tparamerters num: 10000\n",
      "Linear_03\tparamerters num: 100\n",
      "Linear_04\tparamerters num: 10000\n",
      "Linear_04\tparamerters num: 100\n",
      "Linear_05\tparamerters num: 10000\n",
      "Linear_05\tparamerters num: 100\n",
      "Linear_06\tparamerters num: 10000\n",
      "Linear_06\tparamerters num: 100\n",
      "Linear_07\tparamerters num: 10000\n",
      "Linear_07\tparamerters num: 100\n",
      "Linear_08\tparamerters num: 10000\n",
      "Linear_08\tparamerters num: 100\n",
      "Linear_09\tparamerters num: 10000\n",
      "Linear_09\tparamerters num: 100\n",
      "Linear_10\tparamerters num: 10000\n",
      "Linear_10\tparamerters num: 100\n",
      "Linear_11\tparamerters num: 10000\n",
      "Linear_11\tparamerters num: 100\n",
      "Linear_12\tparamerters num: 10000\n",
      "Linear_12\tparamerters num: 100\n",
      "Linear_13\tparamerters num: 10000\n",
      "Linear_13\tparamerters num: 100\n",
      "Linear_14\tparamerters num: 10000\n",
      "Linear_14\tparamerters num: 100\n",
      "Linear_15\tparamerters num: 10000\n",
      "Linear_15\tparamerters num: 100\n",
      "Linear_16\tparamerters num: 10000\n",
      "Linear_16\tparamerters num: 100\n",
      "Linear_17\tparamerters num: 10000\n",
      "Linear_17\tparamerters num: 100\n",
      "Linear_18\tparamerters num: 10000\n",
      "Linear_18\tparamerters num: 100\n",
      "Linear_19\tparamerters num: 10000\n",
      "Linear_19\tparamerters num: 100\n",
      "Linear_20\tparamerters num: 1000\n",
      "Linear_20\tparamerters num: 10\n"
     ]
    },
    {
     "data": {
      "text/plain": "271410"
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "class NeuralNetwork(nn.Module):\n",
    "    def __init__(self, layers_num=2):\n",
    "        super().__init__()\n",
    "        self.transforms = transforms # 预处理层，标准化\n",
    "        self.flatten = nn.Flatten()\n",
    "        # 多加几层\n",
    "        self.linear_relu_stack = nn.Sequential(\n",
    "            nn.Linear(28 * 28, 100),\n",
    "            nn.ReLU(),\n",
    "        )\n",
    "        # 加19层\n",
    "        for i in range(1, layers_num):\n",
    "            self.linear_relu_stack.add_module(f\"Linear_{i}\", nn.Linear(100, 100))\n",
    "            self.linear_relu_stack.add_module(f\"relu\", nn.ReLU())\n",
    "        # 输出层\n",
    "        self.linear_relu_stack.add_module(\"Output Layer\", nn.Linear(100, 10))\n",
    "        \n",
    "        # 初始化权重\n",
    "        self.init_weights()\n",
    "        \n",
    "    def init_weights(self):\n",
    "        \"\"\"使用 xavier 均匀分布来初始化全连接层的权重 W\"\"\"\n",
    "        # print('''初始化权重''')\n",
    "        for m in self.modules():\n",
    "            # print(m)\n",
    "            # print('-'*50)\n",
    "            if isinstance(m, nn.Linear):#判断m是否为全连接层\n",
    "                # https://pytorch.org/docs/stable/nn.init.html\n",
    "                nn.init.xavier_uniform_(m.weight) # xavier 均匀分布初始化权重\n",
    "                nn.init.zeros_(m.bias) # 全零初始化偏置项\n",
    "        # print('''初始化权重完成''')\n",
    "    def forward(self, x):\n",
    "        # x.shape [batch size, 1, 28, 28]\n",
    "        x = self.transforms(x) #标准化\n",
    "        x = self.flatten(x)  \n",
    "        # 展平后 x.shape [batch size, 28 * 28]\n",
    "        logits = self.linear_relu_stack(x)\n",
    "        # logits.shape [batch size, 10]\n",
    "        return logits\n",
    "total=0\n",
    "for idx, (key, value) in enumerate(NeuralNetwork(20).named_parameters()):\n",
    "    print(f\"Linear_{idx // 2:>02}\\tparamerters num: {np.prod(value.shape)}\") #np.prod是计算张量的元素个数\n",
    "    total+=np.prod(value.shape)\n",
    "total"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "outputs": [
    {
     "data": {
      "text/plain": "tensor([[1., 0., 0., 0., 0.],\n        [0., 1., 0., 0., 0.],\n        [0., 0., 1., 0., 0.]])"
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "w = torch.empty(3, 5)\n",
    "nn.init.eye_(w)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-07-18T08:14:01.335144900Z",
     "start_time": "2024-07-18T08:14:01.295167700Z"
    }
   }
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T08:18:23.231667700Z",
     "start_time": "2024-07-18T08:18:22.410532400Z"
    }
   },
   "outputs": [],
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "        preds = logits.argmax(axis=-1)    # 验证集预测\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "        \n",
    "    acc = accuracy_score(label_list, pred_list)\n",
    "    return np.mean(loss_list), acc\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T08:18:32.648585100Z",
     "start_time": "2024-07-18T08:18:32.401627500Z"
    }
   },
   "outputs": [],
   "source": [
    "from torch.utils.tensorboard import SummaryWriter\n",
    "\n",
    "\n",
    "class TensorBoardCallback:\n",
    "    def __init__(self, log_dir, flush_secs=10):\n",
    "        \"\"\"\n",
    "        Args:\n",
    "            log_dir (str): dir to write log.\n",
    "            flush_secs (int, optional): write to dsk each flush_secs seconds. Defaults to 10.\n",
    "        \"\"\"\n",
    "        self.writer = SummaryWriter(log_dir=log_dir, flush_secs=flush_secs)\n",
    "\n",
    "    def draw_model(self, model, input_shape):\n",
    "        self.writer.add_graph(model, input_to_model=torch.randn(input_shape))\n",
    "        \n",
    "    def add_loss_scalars(self, step, loss, val_loss):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/loss\", \n",
    "            tag_scalar_dict={\"loss\": loss, \"val_loss\": val_loss},\n",
    "            global_step=step,\n",
    "            )\n",
    "        \n",
    "    def add_acc_scalars(self, step, acc, val_acc):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/accuracy\",\n",
    "            tag_scalar_dict={\"accuracy\": acc, \"val_accuracy\": val_acc},\n",
    "            global_step=step,\n",
    "        )\n",
    "        \n",
    "    def add_lr_scalars(self, step, learning_rate):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/learning_rate\",\n",
    "            tag_scalar_dict={\"learning_rate\": learning_rate},\n",
    "            global_step=step,\n",
    "            \n",
    "        )\n",
    "    \n",
    "    def __call__(self, step, **kwargs):\n",
    "        # add loss\n",
    "        loss = kwargs.pop(\"loss\", None)\n",
    "        val_loss = kwargs.pop(\"val_loss\", None)\n",
    "        if loss is not None and val_loss is not None:\n",
    "            self.add_loss_scalars(step, loss, val_loss)\n",
    "        # add acc\n",
    "        acc = kwargs.pop(\"acc\", None)\n",
    "        val_acc = kwargs.pop(\"val_acc\", None)\n",
    "        if acc is not None and val_acc is not None:\n",
    "            self.add_acc_scalars(step, acc, val_acc)\n",
    "        # add lr\n",
    "        learning_rate = kwargs.pop(\"lr\", None)\n",
    "        if learning_rate is not None:\n",
    "            self.add_lr_scalars(step, learning_rate)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T08:18:38.910670Z",
     "start_time": "2024-07-18T08:18:38.904974600Z"
    }
   },
   "outputs": [],
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=5000, save_best_only=True):\n",
    "        \"\"\"\n",
    "        Save checkpoints each save_epoch epoch. \n",
    "        We save checkpoint by epoch in this implementation.\n",
    "        Usually, training scripts with pytorch evaluating model and save checkpoint by step.\n",
    "\n",
    "        Args:\n",
    "            save_dir (str): dir to save checkpoint\n",
    "            save_epoch (int, optional): the frequency to save checkpoint. Defaults to 1.\n",
    "            save_best_only (bool, optional): If True, only save the best model or save each model at every epoch.\n",
    "        \"\"\"\n",
    "        self.save_dir = save_dir\n",
    "        self.save_step = save_step\n",
    "        self.save_best_only = save_best_only\n",
    "        self.best_metrics = -1\n",
    "        \n",
    "        # mkdir\n",
    "        if not os.path.exists(self.save_dir):\n",
    "            os.mkdir(self.save_dir)\n",
    "        \n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return\n",
    "        \n",
    "        if self.save_best_only:\n",
    "            assert metric is not None\n",
    "            if metric >= self.best_metrics:\n",
    "                # save checkpoints\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"best.ckpt\"))\n",
    "                # update best metrics\n",
    "                self.best_metrics = metric\n",
    "        else:\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T08:18:43.022006800Z",
     "start_time": "2024-07-18T08:18:43.014428900Z"
    }
   },
   "outputs": [],
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-18T08:19:20.271204500Z",
     "start_time": "2024-07-18T08:19:20.261309700Z"
    }
   },
   "outputs": [],
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    "                preds = logits.argmax(axis=-1)\n",
    "            \n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())    \n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"acc\": acc, \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss, val_acc = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"acc\": val_acc, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "                    \n",
    "                    # 1. 使用 tensorboard 可视化\n",
    "                    if tensorboard_callback is not None:\n",
    "                        tensorboard_callback(\n",
    "                            global_step, \n",
    "                            loss=loss, val_loss=val_loss,\n",
    "                            acc=acc, val_acc=val_acc,\n",
    "                            lr=optimizer.param_groups[0][\"lr\"],\n",
    "                            )\n",
    "                    \n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), metric=val_acc)\n",
    "\n",
    "                    # 3. 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(val_acc)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 100\n",
    "\n",
    "model = NeuralNetwork(layers_num=10)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "outputs": [],
   "source": [
    "# 1. 定义损失函数 采用交叉熵损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "# 2. 定义优化器 采用SGD\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)\n",
    "\n",
    "# 1. tensorboard 可视化\n",
    "tensorboard_callback = TensorBoardCallback(\"runs\")\n",
    "tensorboard_callback.draw_model(model, [1, 28, 28])\n",
    "# 2. save best\n",
    "save_ckpt_callback = SaveCheckpointsCallback(\"checkpoints\", save_best_only=True)\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=10, min_delta=0.001)\n",
    "\n",
    "model = model.to(device)"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-07-18T08:19:45.450320900Z",
     "start_time": "2024-07-18T08:19:45.175435500Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "outputs": [],
   "source": [
    "record = training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    val_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    "    )"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-11-17T13:06:44.722561800Z",
     "start_time": "2023-11-17T13:06:44.373481400Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": "<Figure size 864x360 with 2 Axes>",
      "image/png": "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\n"
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    fig_num = len(train_df.columns)\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(6 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):    \n",
    "        axs[idx].plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        axs[idx].grid()\n",
    "        axs[idx].legend()\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "    \n",
    "    plt.show()\n",
    "\n",
    "plot_learning_curves(record, sample_step=10000)  #横坐标是 steps"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-04-24T03:29:22.432377300Z",
     "start_time": "2024-04-24T03:29:18.976354400Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.4376\n",
      "accuracy: 0.8839\n"
     ]
    }
   ],
   "source": [
    "# dataload for evaluating\n",
    "\n",
    "# load checkpoints\n",
    "model.load_state_dict(torch.load(\"checkpoints/best.ckpt\", map_location=\"cpu\"))\n",
    "\n",
    "model.eval()\n",
    "loss, acc = evaluating(model, val_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\\naccuracy: {acc:.4f}\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "pytorch",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.8"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
